Laboratorio 2: Exploración y Visualización de Datos¶
Fecha: Marzo 2025
Integrantes: Alina González (Sec 1) y Sergio Romero (Sec 1)
Instrucciones¶
Trabajen en equipos de dos personas. Salvo excepciones, no se corregirá entregas con menos de dos integrantes.
Modifique este archivo
.ipynbagregando sus respuestas donde corresponda. Puede ocupar Jupyter notebook en su computador o usar Google Colab como alternativa online.Para cada pregunta incluya el código fuente que utilizó para llegar a su respuesta. Respuestas sin código no recibirán puntaje..
El formato de entrega para esta actividad es un archivo html. Genere un archivo HTML usando Jupyter y súbalo a U-Cursos. Basta con que un/a integrante haga la entrega. Si ambos/as hacen una entrega en U-Cursos, se revisará cualquiera de éstas.
Accidentes de tránsito¶
Para esta sección utilizaremos un dataset real de número de accidentes de tránsito por localidad, el cual puede ser encontrado en el siguiente link: http://datos.gob.cl/dataset/9348. Para cargar el dataset ejecute el siguiente código:
import pandas as pd
import matplotlib.pyplot as plt
import plotly.express as px
import plotly.io as pio
tipos = pd.read_csv("https://users.dcc.uchile.cl/~hsarmien/mineria/datasets/accidentes_2010_2011.txt", sep=" ")
tipos.head()
| Muestra | Descripcion | Anio | TipoAccidente | Cantidad | |
|---|---|---|---|---|---|
| 1 | Nacional | Nacional | 2010 | Atropello | 8247 |
| 2 | Nacional | Nacional | 2011 | Atropello | 8339 |
| 3 | Regional | XV Región Arica y Parinacota | 2010 | Atropello | 115 |
| 4 | Regional | XV Región Arica y Parinacota | 2011 | Atropello | 159 |
| 5 | Comunal | ARICA | 2010 | Atropello | 115 |
Explore el set de datos para responder las siguientes preguntas:
- ¿Cuáles son las dimensiones del dataset (filas, columnas)?
print(f"filas: {tipos.shape[0]} columnas: {tipos.shape[1]}")
filas: 4296 columnas: 5
- ¿Qué describe cada línea del dataset? (ejemplifique tomando el dato de la fila 235, extienda la descripción)
tipos.iloc[235]
# Cada linea describe la cantidad de accidentes en una muestra territorial de cierto tipo de accidente en un cierto año
# Ejemplo: la linea 235 que hubieron 6 atropellos en palmilla en el año 2011
Muestra Comunal Descripcion PALMILLA Anio 2011 TipoAccidente Atropello Cantidad 6 Name: 236, dtype: object
- ¿Cuántos años diferentes abarca la información del dataset? Entregue un array con los años.
tipos['Anio'].unique()
array([2010, 2011])
Filtre los datos para incluir sólo los accidentes ocurridos el año 2011 a nivel Regional. Luego, genere un gráfico de barras que muestre la cantidad de accidentes en cada Región. Comente sus principales observaciones y si considera que es razonable usar el conteo de frecuencias para determinar las regiones más peligrosas en cuanto a accidentes de tránsito.
OJO: hay que sumar la cantidad de accidentes para los distintos tipos de accidente de una misma región
afect2011 = tipos[(tipos['Muestra'] == 'Regional') & (tipos['Anio'] == 2011)]
plt.bar(afect2011['Descripcion'], afect2011['Cantidad'])
plt.xlabel('Descripción')
plt.ylabel('Cantidad')
plt.title('Cantidad de Muertos en Accidentes Regionales en 2011')
plt.xticks(rotation=45, ha='right')
plt.show()
R: La Región Metropolitana es la con mayor cantidad de accidentes, seguida de la región de Valparaíso. También hay 2 regiones VII. No es razonable el conteo de frecuencia para ver las regiones más peligrosas en accidentes de tránsito, ya que no considera población ni cantidad de autos en circulación.
- Filtre los datos para incluir sólo los accidentes ocurridos el año 2010 a nivel regional. Genere un boxplot donde se indique la cantidad de accidentes categorizado por tipo de accidente. ¿Cuáles son sus principales observaciones?
afect2010 = tipos[(tipos['Anio'] == 2010) & (tipos['Muestra'] == 'Regional')]
fig = px.box(afect2010, x='TipoAccidente', y='Cantidad')
fig.show(renderer='notebook')
R: Existen múltiples outliars debido a que en una región suceden muchos más accidentes de tránsito que en el resto.
- ¿Qué otra forma de explorar los datos podría agregar para el dataset de Accidentes de tránsito y qué información adicional aporta? Adjunte el código necesario.
R: Podrían revisarse las estadísticas de Cantidad con describe. Aporta datos sobre los cuales plantear preguntas sobre el dataset.
tipos.describe(include="all")
| Muestra | Descripcion | Anio | TipoAccidente | Cantidad | |
|---|---|---|---|---|---|
| count | 4296 | 4296 | 4296.000000 | 4296 | 4296.000000 |
| unique | 3 | 359 | NaN | 6 | NaN |
| top | Comunal | PENAFLOR | NaN | Atropello | NaN |
| freq | 4104 | 12 | NaN | 716 | NaN |
| mean | NaN | NaN | 2010.500000 | NaN | 84.203911 |
| std | NaN | NaN | 0.500058 | NaN | 835.751218 |
| min | NaN | NaN | 2010.000000 | NaN | 0.000000 |
| 25% | NaN | NaN | 2010.000000 | NaN | 1.000000 |
| 50% | NaN | NaN | 2010.500000 | NaN | 5.000000 |
| 75% | NaN | NaN | 2011.000000 | NaN | 20.000000 |
| max | NaN | NaN | 2011.000000 | NaN | 31487.000000 |
También podemos analizar la cantidad de accidentes por tipo en cada región y ver como se distribuyen.
# for region in tipos[tipos['Muestra'] == 'Regional']['Descripcion'].unique():
# df = tipos[tipos['Descripcion'] == region ].groupby('TipoAccidente', as_index=False)['Cantidad'].sum()
# fig = px.line_polar(df, r='Cantidad', theta='TipoAccidente', line_close=True)
# fig.update_layout(
# title = f'Accidentes por tipo en {region}', font_size = 13, width=500, height=300
# )
# fig.show(renderer='notebook')
# DEJAMOS UN PURO GRÁFICO PORQUE SON MUY PESADOS, SI SON TODOS EL HTML PESA 150MB XD
df = tipos[tipos['Descripcion'] == 'Región Metropolitana' ].groupby('TipoAccidente', as_index=False)['Cantidad'].sum()
fig = px.line_polar(df, r='Cantidad', theta='TipoAccidente', line_close=True)
fig.update_layout(
title = f'Accidentes por tipo en Región Metropolitana', font_size = 13, width=500, height=300
)
fig.show(renderer='notebook')
Los gráficos nos muestran que distintas regiones poseen distintas distribuiciones de accidentes de tráfico
Diabetes¶
Considere el set de datos de pacientes para la predicción de diabetes con las siguientes columnas:
- gender: género del paciente
- age: edad del paciente
- hypertension: indica si el paciente tiene o no hipertensión
- heart_disease: indica si el paciente tiene o no enfermedad cardiaca
- smoking_history: indica si el paciente es o fue fumador
- bmi: indice de masa corporal del paciente
- HbA1c_level: Hemoglobina HbA1c del paciente
- blood_glucose_level: Nivel de glucosa en sangre del paciente
- diabetes: si el paciente tiene o no diabetes
diabetes = pd.read_csv("https://raw.githubusercontent.com/mzambrano1/Datasets-CC5205-otono-2023/master/lab1.2%202023-2/diabetes_prediction_dataset.csv")
diabetes.head()
| gender | age | hypertension | heart_disease | smoking_history | bmi | HbA1c_level | blood_glucose_level | diabetes | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | Female | 80.0 | 0 | 1 | never | 25.19 | 6.6 | 140 | 0 |
| 1 | Female | 54.0 | 0 | 0 | No Info | 27.32 | 6.6 | 80 | 0 |
| 2 | Male | 28.0 | 0 | 0 | never | 27.32 | 5.7 | 158 | 0 |
| 3 | Female | 36.0 | 0 | 0 | current | 23.45 | 5.0 | 155 | 0 |
| 4 | Male | 76.0 | 1 | 1 | current | 20.14 | 4.8 | 155 | 0 |
- Para explorar el dataset, realice un análisis de frecuencias de los atributos categóricos (categorías binarias y multiclase).
for col in (['gender', 'hypertension', 'heart_disease', 'smoking_history', 'diabetes']):
print('-'*80 + '\n')
print(diabetes[col].value_counts())
print('-'*80 + '\n')
-------------------------------------------------------------------------------- gender Female 58552 Male 41430 Other 18 Name: count, dtype: int64 -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- hypertension 0 92515 1 7485 Name: count, dtype: int64 -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- heart_disease 0 96058 1 3942 Name: count, dtype: int64 -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- smoking_history No Info 35816 never 35095 former 9352 current 9286 not current 6447 ever 4004 Name: count, dtype: int64 -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- diabetes 0 91500 1 8500 Name: count, dtype: int64 --------------------------------------------------------------------------------
- Muestre estadísticas de resumen para las variables numéricas y comenten sus observaciones.
diabetes[['age', 'bmi', 'HbA1c_level', 'blood_glucose_level']].describe()
| age | bmi | HbA1c_level | blood_glucose_level | |
|---|---|---|---|---|
| count | 100000.000000 | 100000.000000 | 100000.000000 | 100000.000000 |
| mean | 41.885856 | 27.320767 | 5.527507 | 138.058060 |
| std | 22.516840 | 6.636783 | 1.070672 | 40.708136 |
| min | 0.080000 | 10.010000 | 3.500000 | 80.000000 |
| 25% | 24.000000 | 23.630000 | 4.800000 | 100.000000 |
| 50% | 43.000000 | 27.320000 | 5.800000 | 140.000000 |
| 75% | 60.000000 | 29.580000 | 6.200000 | 159.000000 |
| max | 80.000000 | 95.690000 | 9.000000 | 300.000000 |
Clasificación¶
Ahora crearemos un clasificador binario (por ahora no importa cómo funciona), y veremos que tal es su desempeño decidiendo si una persona tiene diabetes o no.
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
# Droppear categoricas
df_clf = diabetes.drop(columns=['gender', 'smoking_history'])
# Separar atributos y target
X = df_clf.drop(columns=['diabetes'])
y = df_clf['diabetes']
# Separar conjuntos de entrenamiento y test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
# Entrenar modelo
clf = DecisionTreeClassifier(max_depth=3, random_state=0)
clf.fit(X_train, y_train)
# Obtener predicciones
y_pred = clf.predict(X_test)
Veamos su matríz de confusión¶
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['Sin diabetes', 'Diabetes'])
disp.plot(cmap='Blues')
plt.title('Matríz de Confusión')
plt.show()
- ¿Qué se puede decir a partir de la matriz de confusión? ¿El clasificador tiene un problema asociado a precision o recall?
R: El modelo tiene un mal recall (0.67), ya que este considera los falsos negativos, y vemos que existen múltiples predicciones de falso negativo, por lo que el modelo es peligroso al tener gente con diabetes no detectada.
Curva ROC 🤘¶
from sklearn.metrics import roc_curve, auc
y_scores = clf.predict_proba(X_test)[:,1]
fpr, tpr, _ = roc_curve(y_test, y_scores)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, color='blue', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='gray', linestyle='--') # Identidad
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Curva ROC')
plt.legend(loc="lower right")
plt.show()
- ¿Podemos decir que el modelo es bueno según su curva ROC? ¿Para que podemos usar esta visualización?
R: Según la curva ROC no es un mal modelo, pues el área bajo la curva es cercana a 1. Podemos utilizar esta visualización para evaluar el rendimiento, analizando el TPR y FPR.
- ¿Sería seguro usar este clasificador en un caso real diagnosicando pacientes?
R: Si bien el modelo tiene un buen desempeño en cuanto a la curva ROC, consideramos que no es un modelo clasificador seguro para un caso real diagnosticando pacientes, porque en caso de que un paciente SÍ tenga diabetes, el modelo sólo va a predecir correctamente un 67% de las veces.
Y eso es todo por hoy :)